#region Using directives

using System;
using System.Collections.Generic;
using System.Text;

#endregion

namespace Weazel.Gezel.Model
{
  /// <summary>
  /// In gezel, the system blocks are used to specify the
  /// interconnects between the various datapaths and
  /// ip-blocks which are part of the simulation. Multiple
  /// systems can exist in a model. The connection from
  /// a datapath or ip-block outport to one or more
  /// datapath or ip-block inports are represented by
  /// the System.Net nested class.
  /// </summary>
  public partial class system : AbstractEntity<Model>
  {
		public class DatapathConnectorInfo
		{
			private string datapathName;
			public string DatapathName
			{
				get { return datapathName; }
			}

			private int declarationPosition;
			public int DeclarationPosition
			{
				get { return declarationPosition; }
			}

			private string portName;
			public string PortName
			{
				get { return portName; }
			}

			public bool UsingNamedBinding
			{
				get
				{
					return portName != null;
				}
			}

			public DatapathConnectorInfo(string datapathName, int declarationPosition)		
			{
				this.datapathName = datapathName;
				this.declarationPosition = declarationPosition;

				System.Diagnostics.Debug.Assert(datapathName != null);
			}

			public DatapathConnectorInfo(string datapathName, string portName)
			{
				this.datapathName = datapathName;
				this.portName = portName;

				System.Diagnostics.Debug.Assert(portName != null);
				System.Diagnostics.Debug.Assert(datapathName != null);
			}

			public DatapathConnectorInfo(Port port)
			{
				// parent of port is ether dp or ip block
				System.Diagnostics.Debug.Assert(port.Parent is Datapath || port.Parent is IPBlock);
				
				this.datapathName = port.Parent.LocalName;
				this.portName = port.LocalName;

				System.Diagnostics.Debug.Assert(port.LocalName != null);
				System.Diagnostics.Debug.Assert(port.Parent != null);
			}
		}

		public class DatapathConnectionInfo : AbstractEntity<system>
		{
			private List<DatapathConnectorInfo> connectors =
				new List<DatapathConnectorInfo>();
			public List<DatapathConnectorInfo> Connectors
			{
				get { return connectors; }				
			}

			public DatapathConnectionInfo(
				system parent,
				string name
			)
				: base(parent, name)
			{
			}

			public DatapathConnectionInfo(
				system parent
			)
				: base(parent)
			{
			}			
		}

    /// <summary>
    /// The net class represents a connection from an 
    /// outport or external stimuli to one or more
    /// inports. Nets are declared implicitly in Gezel
    /// when connecting components in the "system" block.
    /// 
    /// The inports must be driven by either an outport
    /// or a stimuli but not both.
    /// </summary>
    public class Net : AbstractEntity<system>
    {
      /// <summary>
      /// The stimuli block which drives the
      /// inport(s).
      /// </summary>
      private readonly Stimulus stimulus;
      public Stimulus Stimulus
      {
        get
        {
          return stimulus;
        }
      }

      /// <summary>
      /// The output port which drives the
      /// inport(s).
      /// </summary>
      private OutPort outport;
      public OutPort OutPort
      {
        get
        {
          return outport;
        }
        set
        {
          outport = value;
        }
      }

      /// <summary>
      /// The inports which are connected by this net.
      /// </summary>
      private readonly List<InPort> inports;
      public List<InPort> InPorts
      {
        get
        {
          return inports;
        }
      }

      /// <summary>
      /// Constructs a new system net with the 
      /// specified local name and parent.
      /// </summary>
      /// <param name="parent">containing system</param>
      /// <param name="name">local name</param>
      public Net(system parent, string name) : base(parent, name)
      {
        this.inports = new List<InPort>();
        this.stimulus = null;
      }

      /// <summary>
      /// Constructs a new system net which are driven
      /// by external stimuli.
      /// </summary>
      /// <param name="parent">containing system</param>
      /// <param name="stimulus">external stimuli driver</param>
      public Net(system parent, Stimulus stimulus) : base(parent, stimulus.SignalRef)
      {
        this.inports = new List<InPort>();
        this.stimulus = stimulus;
      }

      /// <summary>
      /// Returns true iff the net has a driver (stimuli or outport) and false otherwise.
      /// </summary>
      /// <returns>true iff the net has a driver</returns>
      public bool HasDriver()
      {
        return outport != null || stimulus != null;
      }
    }

    /// <summary>
    /// The list of system nets hashed on local name.
    /// </summary>
    private readonly Dictionary<string, Net> nets;
    public Dictionary<string, Net> Nets
    {
      get
      {
        return nets;
      }
    }

		private Dictionary<string, DatapathConnectionInfo> portConnections;
		public Dictionary<string, DatapathConnectionInfo> PortConnections
		{
			get { return portConnections; }
		}

		private List<string> unconnectedDatapaths;
		public List<string> UnconnectedDatapaths
		{
			get { return unconnectedDatapaths; }			
		}

		///// <summary>
		///// The list of contained datapaths hashed on local name.
		///// </summary>
		//private Dictionary<string, string[]> datapathRefs;
		//public Dictionary<string, string[]> DatapathRefs
		//{
		//  get
		//  {
		//    return datapathRefs;
		//  }
		//}

    /// <summary>
    /// The set of stimuli blocks hashed on the name of the
    /// net they drive.
    /// </summary>
    private Dictionary<string, Stimulus> stimuli;
    public Dictionary<string, Stimulus> Stimuli
    {
      get
      {
        return stimuli;
      }
    }

		/// <summary>
		/// Returns a lsit of all datapaths referenced by this system
		/// </summary>
		/// <returns></returns>
		public string[] GetDatapathReferenceNamed()
		{
			List<string> result = new List<string>();

			foreach (DatapathConnectionInfo connection in portConnections.Values)
			{
				foreach (DatapathConnectorInfo connector in connection.Connectors)
				{
					if (!result.Contains(connector.DatapathName))
						result.Add(connector.DatapathName);
				}
			}

			return result.ToArray();
		}

		/// <summary>
		/// Return a list of net names bound to a specific datapath in the order
		/// of which the port is declared.
		/// </summary>
		/// <param name="datapathName"></param>
		/// <returns></returns>
		public string[] GetPositionalBindingList(string blockName)
		{
			Block block = parent.GetBlock(blockName);

			string[] ports =
				new string[block.InPorts.Count + block.OutPorts.Count];

			foreach (DatapathConnectionInfo connection in portConnections.Values)
			{
				foreach (DatapathConnectorInfo connector in connection.Connectors)
				{
					if (connector.DatapathName == blockName)
					{
						if (connector.UsingNamedBinding)
						{
							int position = block.GetPortByLocalName(connector.PortName).DeclarationPosition;
							ports[position] = connection.LocalName;
						}
						else
						{
							ports[connector.DeclarationPosition] = connection.LocalName;
						}
					}
				}
			}

			foreach (string port in ports)
				if (port == null)
					throw new NullReferenceException("Null reference in datapath positional binding list (" + blockName + ")");

			return ports;
		}

		public system(Model parent, string name, Position startPosition, Position endPosition)
			: 
      this(parent, name)
    {
      this.startPosition = startPosition;
			this.endPosition = endPosition;
    }

    /// <summary>
    /// Constructs a new system with the model as parent
    /// and with the given local name.
    /// </summary>
    /// <param name="parent">containing model</param>
    /// <param name="name">local name</param>
    public system(Model parent, string name) : base(parent, name)
    {
			portConnections = new Dictionary<string, DatapathConnectionInfo>();
			unconnectedDatapaths = new List<string>();
    //  datapathRefs = new Dictionary<string, string[]>();
      stimuli = new Dictionary<string, Stimulus>();
      nets = new Dictionary<string, Net>();
    }
  }
}